Sorting out some pain points with kickstart
In the last post, I used kickstart.nvim to set up a minimal Rust IDE, in preparation for Advent of Code this year. Then I said I'd leave the topic be until December, unless I can't wait and continue work on backfilling all 200 solutions for past years.
Well, I did the latter, which made clear some pain points for me, including the stuff I listed at the end last time. Some new items joined the list since, mostly very practical editing-related ones. So, what have I done to the config?
Open points from the previous post
go through all plugins included in kickstart.nvim
; learn about them, or prune them
I haven't quite done all of them, but most of them.
tpope/vim-fugitive
Adds the :Git
command which is kind of like :!git
(the raw command line invocation) but slightly nicer, output-wise. :Git
itself also opens a view (similar to netrw
) where I can individually stage files, see changes in them, etc. It's very nice. It stays, since I've been actively using it.
tpope/vim-rhubarb
Githhub extension for the previous one. I've not used it, and I rarely use Github in general (all my repos I host myself, at most with a secondary github
origin I remember to push to occasionally). If Github keeps pushing into AI, I may end up never using this plugin[1]. But for now, it stays.
tpope/vim-sleuth
Automatically adjusts tab width and the like to match the file you're editing. I think. Seems like a minor quality of life thing, I'll keep it.
All of the lsp stuff
neovim/nvim-lspconfig, williamboman/mason.nvim, williamboman/mason-lspconfig.nvim, j-hui/fidget.nvim, folke/neodev.nvim
I did modify a couple settings in lua/lsp-config.lua
, which mostly have to do with these, but I've done minimal reading about them so far. Going in-depth will probably require a whole post to themselves.
All of the autocomplete stuff
hrsh7th/nvim-cmp, L3MON4D3/LuaSnip, saadparwaiz1/cmp_luasnip, hrsh7th/cmp-nvim-lsp, rafamadriz/friendly-snippets
As above. I have a reasonable idea of the general role they fill, and I've modified some config (more about that further down!), but I've not really read much into any of these.
folke/which-key.nvim
Absolute life-saver. When you start a multi-key sequence, a cheatsheet pops up that shows you what can come next. Useful for exploratory use, useful for getting used to new keybinds, overall just useful. I don't know if I'll keep it forever, but for now it's essential to me.
lewis6991/gitsigns.nvim
Does little marks to the left of each line if they're added or changed. Relatively small thing, and I don't rely on it often, but I'd miss it if it was gone. Stays.
navarasu/onedark.nvim
A theme. I'll probably replace it eventually, but for now I don't have the energy to go shopping for themes.
nvim-lualine/lualine.nvim
A better status line. I turned it off once to compare, and it is so much prettier. I briefly skimmed the docs, and it seems you can customize what gets shown. I'm very happy with it is right now, but knowing what my options are here would be cool down the line.
lukas-reineke/indent-blankline.nvim
Adds indentation guides. Integrates with treesitter
and your LSP to highlight the current scope. I like it.
numToStr/Comment.nvim
Adds a binding for gc
which lets you comment-toggle things. Integrates with all the motions and text objects as you'd expect, and it uses the active LSP to pick the right characters to make things commments. Actively used this already.
nvim-telescope/telescope.nvim
Using Obsidian[2], I already got used to not having a file explorer view open. I prefer opening a file searcher, typing in part of the name I'm looking for, and picking the one I meant from a narrowed-down list.
Telescope is that, but much more versatile. It lets me fuzzy find files or open buffers by name (though I had to add a keybind for the latter on my own), it integrates neatly with git
by providing a command to grep from the root of the repository, and it lets me search for symbols in the workspace of the active LSP, with the same fuzzy-finding superpowers.
It's hard to understate just how much I rely on this plugin to do anything now. Only thing I use netrw
(the built-in file explorer) for sometimes is creating new files.
nvim-treesitter/nvim-treesitter
So yeah, this thing parses a document in to an AST, essentially. A bunch of the previous plugins use that information to work right. But it's not just a dependency: it also provides a bunch of really useful code-oriented textobjects, like function
or parameter
or statement
, which I've been using a bunch.
So yeah, that's all the plugins that came with it by default. I kind of skipped the two biggest chunks (lsp and cmp), but nonetheless I'd say I was rather thorough. I was surprised that (almost) all of the plugins were of immediate use to me—though some only because I took the time to read what they do.
enable format-on-save for code files
In lua/lsp-config.lua
, there's an on_attach
handler that gets used on every buffer that has an associated LSP. I just added this bit to the end of it:
-- Create a command `:ToggleFormatOnSave` local to the LSP buffer
local autoformat_enabled = true
vim.api.nvim_create_user_command('ToggleFormatOnSave', function()
autoformat_enabled = not autoformat_enabled
print('Format on save: ' .. tostring(autoformat_enabled))
end, {})
vim.api.nvim_create_autocmd('BufWritePre', {
buffer = bufnr,
callback = function()
if autoformat_enabled then
vim.lsp.buf.format {
async = false,
}
end
end
})
kickstart.nvim
has an autoformat plugin, but it did too much, covering use cases that don't apply to me—so I just scavenged the parts that interest me. Writing both a :Command
and an autocmd
was nice. I'm not sure I'll need :ToggleFormatOnSave
, but eh. I'll be glad to have it when I do need it.
use cargo clippy
to produce diagnostics
My hunch was I could just configure it on the servers
list in lua/lsp-config.lua
, and I was right:
rust_analyzer = {
["rust-analyzer"] = {
checkOnSave = {
command = "clippy",
},
},
},
Not much else to say here. I love clippy.
review motions so I don't hold j
/k
to navigate through files
I still do that, but I turned on relativenumber
, so now my line numbers are relative, which helps with picking the right number for number-prefixed motions. I now know that [
and ]
are a thing, and with the help of which-key, I'm slowly getting used to using them more.
The main thing that I need to look up now is how to scroll the screen without going to the edge with my cursor. I think it's also possible to configure a scroll-off threshold or something like that? So it starts scrolling before I reach the very top or bottom. I'll note that down for next time.
New stuff that was annoying me
By actually using the editor to code, I noticed a couple things that were annoying me.
Solved
Some of them I immediately solved, either because they were very low-hanging fruit, or got in my way that much.
Autocompletion too eager
There's plenty of snippets I have access to from various preconfigured snippet sources. That means I frequently get offered them, which usually isn't a problem. I'm not that bothered by a dropdown following my cursor.
However, they way it was configured, Enter
would accept the first item in the list, even if I hadn't selected anything. Particularly when writing comments, I would often accidentally mangle it by inserting a snippet that's partially comment:
Pressing Enter
now would give me this:
Obvious nonsense! I thought I would have to mess with the Enter
keybind (and in a way I was right), but reading some more docs, turned out that's simply a setting. lua/lsp-config.lua
also has config for all the autocompletion stuff, and therein lay the culprit:
['<CR>'] = cmp.mapping.confirm {
behavior = cmp.ConfirmBehavior.Replace,
select = true,
},
Taken from the mapping
table in the call to cmp.setup({...})
. The select
here makes it so that if nothing was selected, it automatically selects the first item in the displayed autocompletion list. Which is the exact thing that's been bothering me! Changed it to false
, and now snippets only happen if I pick something. Good improvement.
No good way to select an entire control flow statement with its block
For moving, copying, deleting, whatever. It would make sense for this to be a text object, and since treesitter
is the thing that provides code-related text objects, I read its docs a bit more, and found that it indeed has one, called @statement.outer
.
textobjects = {
select = {
...
keymaps = {
-- You can use the capture groups defined in textobjects.scm
['aa'] = '@parameter.outer',
['ia'] = '@parameter.inner',
['af'] = '@function.outer',
['if'] = '@function.inner',
['ac'] = '@class.outer',
['ic'] = '@class.inner',
['ae'] = '@statement.outer',
},
},
move = {
enable = true,
set_jumps = true, -- whether to set jumps in the jumplist
goto_next_start = {
[']m'] = '@function.outer',
[']]'] = '@class.outer',
[']e'] = '@statement.outer',
},
goto_next_end = {
[']M'] = '@function.outer',
[']['] = '@class.outer',
[']E'] = '@statement.outer',
},
goto_previous_start = {
['[m'] = '@function.outer',
['[['] = '@class.outer',
['[e'] = '@statement.outer',
},
goto_previous_end = {
['[M'] = '@function.outer',
['[]'] = '@class.outer',
['[E'] = '@statement.outer',
},
},
...
}
Added a keybind for it (ae
, as in outer state
ment... well, mostly because e
is easy to access and free), as well as ]e
, ]E
, [e
, and [E
, to navigate to the beginning/end of the next/previous statement. Problem solved!
Can't say I understand all of it, but the keybinds work!
Low-hanging fruit
While perusing the configs, I found the list of treesitter
grammars to install (it's called ensure_installed
in lua/treesitter-config.lua
). Removed the ones I don't care about, like cpp
, javascript
, typescript
, etc; and added markdown
instead. I have a feeling I might need that one soon.
Unsolved
Pairing delimiters
I'm used to the behaviour that typing an opening delimiter automatically places the matching closing delimiter, too; and then, typing a closing delimiter when they're balanced simply passes over it instead of adding an additional one.
This is such a common thing that I'm sure there's a plugin for it, I just haven't gotten around to finding it yet.
Writing into an empty block
This may be a pecularity of rust-fmt
, but I occasionally run into this situation:
if condition {}
It loves putting empty blocks on the same line like that. Now, how do I start writing stuff into it? I can navigate to the {
, press o
, and now it looks like this:
if condition {
typing here}
I drag the closing brace with me whilst typing. Annoying! I would expect it to become this:
if condition {
typing here
}
I'm hoping that when I get around to getting a delimiter plugin, that'll also be solved.
Diagnostics listing only shows problems in current file
I think that's a decent default behaviour, but I haven't found a way to show a full diagnostics list for the entire workspace yet. It's neceessary sometimes, especially during refactors.
LSP action rename
doesn't save files
This is fine when you're renaming something small within the current scope, but with larger workspace-wide renames, that can screw you real quick. I assume this is ignorance on my part, and there's a way to do that.
Scroll-off threshold
I hate having to scroll to the very edge before the view starts scrolling. Should be like a quarter of the screen off the edge. I think this is just a simple setting, I should just do it.
Scrolling without moving cursor (too much)
From doing vim-tutor
ages ago, I remember there's buttons for it. I need to look them up, lmao.
Summary
Overall, I'm feeling quite good with my config-fu by now. Adding a whole text object to my repertoire? That's not basic config stuff.
I'm also feeling more and more at home in Neovim in general. I think modal editing meshes well with how I think in the first place.
I still haven't really added any new plugins of my own yet, but I've been eyeing simrat39/rust-tools.nvim. I may try setting it up properly next time.